#include <cstring>
#include "DrawableObject.hpp"
#include "ColorIdGenerator.hpp"

DrawableObject::DrawableObject(): _nbVertices(0), _nbIndex(0), _vertices(NULL), _index(NULL), _shader(NULL)
{
    _position.init();
}

DrawableObject::DrawableObject(Shader& shader): _nbVertices(0), _nbIndex(0), _vertices(NULL), _index(NULL), _shader(&shader)
{
}

DrawableObject::DrawableObject(DrawableObject const& copy)
{
    _nbVertices = copy._nbVertices;
    _nbIndex = copy._nbIndex;

    if (copy._vertices != NULL)
    {
        ColorIdGenerator    generator;

        generator.generate();
        _vertices = new VertexAttributes[_nbVertices];
        for (unsigned int it = 0; it < _nbVertices; it++)
        {
            _vertices[it].posX = copy._vertices[it].posX;
            _vertices[it].posY = copy._vertices[it].posY;
            _vertices[it].posZ = copy._vertices[it].posZ;

            _vertices[it].normX = copy._vertices[it].normX;
            _vertices[it].normY = copy._vertices[it].normY;
            _vertices[it].normZ = copy._vertices[it].normZ;

            _vertices[it].idX = generator.getR();
            _vertices[it].idY = generator.getG();
            _vertices[it].idZ = generator.getB();

            _vertices[it].colorR = copy._vertices[it].colorR;
            _vertices[it].colorG = copy._vertices[it].colorG;
            _vertices[it].colorB = copy._vertices[it].colorB;
        }
    }
    else
        _vertices = copy._vertices;

    if (copy._index != NULL)
    {
        _index = new unsigned int[_nbIndex];
        for (unsigned int it = 0; it < _nbIndex; it++)
        {
            _index[it] = copy._index[it];
        }
    }
    else
        _index = copy._index;

    _shader = copy._shader;
    for (unsigned int it = 0; it < VBO_SIZE; it++)
        _vbo[it] = copy._vbo[it];
    for (unsigned int it = 0; it < ATTRIB_SIZE; it++)
        _attrib[it] = copy._attrib[it];
    _position = copy._position;

    init();
}

DrawableObject::~DrawableObject(void)
{
    glDeleteBuffers(VBO_SIZE, _vbo);
    delete[] _vertices;
    delete[] _index;
}

void	DrawableObject::init(void)
{
	// Create VBO
    glGenBuffers(VBO_SIZE, _vbo);
	
	// Init VBO for index
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo[VBO_INDEX]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * _nbIndex, _index, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	// Init VBO for vertex
	glBindBuffer(GL_ARRAY_BUFFER, _vbo[VBO_VERTEX]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(VertexAttributes) * _nbVertices, _vertices, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void    DrawableObject::shaderAttributesGet()
{
    _attrib[ATTRIB_VERTEX] = _shader->getAttribLocation("in_Vertex");
    _attrib[ATTRIB_COLOR] = _shader->getAttribLocation("in_Color");
    _attrib[ATTRIB_NORMAL] = _shader->getAttribLocation("in_Normal");
    _attrib[ATTRIB_TEXT] = _shader->getAttribLocation("in_Texture");
    _attrib[ATTRIB_ID] = _shader->getAttribLocation("in_Id");
}

void    DrawableObject::shaderAttributesEnable()
{
    glEnableVertexAttribArray(_attrib[ATTRIB_VERTEX]);
    glEnableVertexAttribArray(_attrib[ATTRIB_COLOR]);
    glEnableVertexAttribArray(_attrib[ATTRIB_NORMAL]);
    glEnableVertexAttribArray(_attrib[ATTRIB_TEXT]);
    glEnableVertexAttribArray(_attrib[ATTRIB_ID]);
}

void    DrawableObject::shaderAttributesDisable()
{
    glDisableVertexAttribArray(_attrib[ATTRIB_ID]);
    glDisableVertexAttribArray(_attrib[ATTRIB_TEXT]);
    glDisableVertexAttribArray(_attrib[ATTRIB_NORMAL]);
    glDisableVertexAttribArray(_attrib[ATTRIB_COLOR]);
    glDisableVertexAttribArray(_attrib[ATTRIB_VERTEX]);
}

void    DrawableObject::shaderAttributesSendData()
{
    glVertexAttribPointer(_attrib[ATTRIB_VERTEX], 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), BUFFER_OFFSET(0));
    glVertexAttribPointer(_attrib[ATTRIB_COLOR], 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), BUFFER_OFFSET(sizeof(float) * 3));
    glVertexAttribPointer(_attrib[ATTRIB_NORMAL], 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), BUFFER_OFFSET(sizeof(float) * 6));
    glVertexAttribPointer(_attrib[ATTRIB_TEXT], 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), BUFFER_OFFSET(sizeof(float) * 9));
    glVertexAttribPointer(_attrib[ATTRIB_ID], 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), BUFFER_OFFSET(sizeof(float) * 12));
}

void	DrawableObject::begin(void)
{
    glBindBuffer(GL_ARRAY_BUFFER, _vbo[VBO_VERTEX]);
    shaderAttributesGet();
    shaderAttributesEnable();
    shaderAttributesSendData();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo[VBO_INDEX]);

    _shader->sendUniformData("UniObjectPosition", _position);
}

void	DrawableObject::draw(void)
{
	begin();
    glDrawElements(GL_TRIANGLES, _nbIndex, GL_UNSIGNED_INT, 0);
    end();
}

void	DrawableObject::end(void)
{
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    shaderAttributesDisable();
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void    DrawableObject::updateVBO()
{
    glBindBuffer(GL_ARRAY_BUFFER, _vbo[VBO_VERTEX]);
    void*   vboData = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    if (vboData != NULL)
    {
        memcpy(vboData, _vertices, _nbVertices * sizeof(VertexAttributes));
    }
    glUnmapBuffer(GL_ARRAY_BUFFER);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void				DrawableObject::setShader(Shader* shader)
{
	_shader = shader;
}

Shader*             DrawableObject::getShader()
{
    return _shader;
}


void				DrawableObject::setVertices(unsigned int nbVertices, VertexAttributes* vertexAttributes)
{
	_nbVertices = nbVertices;
	_vertices = vertexAttributes;
}

void                DrawableObject::setNbVertices(const unsigned int nbVertices)
{
    _nbVertices = nbVertices;
}


unsigned int		DrawableObject::getNbVertices() const
{
	return _nbVertices;
}


VertexAttributes*	DrawableObject::getVertices(void)
{
	return _vertices;
}

void				DrawableObject::setIndex(unsigned int nbIndex, unsigned int* index)
{
	_nbIndex = nbIndex;
	_index = index;
}

void                DrawableObject::setNbIndex(const unsigned int nbIndex)
{
    _nbIndex = nbIndex;
}

unsigned int		DrawableObject::getNbIndex() const
{
	return _nbIndex;
}

unsigned int*		DrawableObject::getIndex(void)
{
	return _index;
}

VertexAttributes*   DrawableObject::findVertex(const unsigned char r, const unsigned char g, const unsigned char b, int &index)
{
    for (unsigned int it = 0; it < _nbVertices; it++)
    {
        unsigned char   objectR = static_cast<unsigned char>(_vertices[it].idX);
        unsigned char   objectG = static_cast<unsigned char>(_vertices[it].idY);
        unsigned char   objectB = static_cast<unsigned char>(_vertices[it].idZ);

        if (objectR == r && objectG == g && objectB == b)
        {
            index = it;
            return &_vertices[it];
        }
    }
    return NULL;
}

void            DrawableObject::translate(const float x, const float y, const float z)
{
    _position[0] += x;
    _position[1] += y;
    _position[2] += z;
}

Vec3f&          DrawableObject::getPosition()
{
    return _position;
}

void            DrawableObject::setPositionX(const float x)
{
    _position[0] = x;
}

void            DrawableObject::setPositionY(const float y)
{
    _position[1] = y;

}

void            DrawableObject::setPositionZ(const float z)
{
    _position[2] = z;
}

void            DrawableObject::setPosition(const float x, const float y, const float z)
{
    _position[0] = x;
    _position[1] = y;
    _position[2] = z;
}
